package fem2.mesh_and_topology;

import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

import fem2.Element;
import fem2.Mesh;

/**
 * index integration points of the model using QuadTree or OctTree
 * 
 * @author hbui
 * 
 */
public class IntegrationPointIndexer {

	// private static IntegrationPointIndexer instance = new
	// IntegrationPointIndexer();
	//
	// private IntegrationPointIndexer() {
	// }
	//
	// /**
	// * Get the instance of IntegrationPointSpatialIndexer singleton
	// *
	// * @return the Singleton instance
	// */
	// public static IntegrationPointIndexer getInstance() {
	// return instance;
	// }

	private SpatialTree indexer;
	private double x0, y0, z0, size;

	public IntegrationPointIndexer(Mesh mesh) {
		this.initialize(mesh);
	}

	/**
	 * initialize the spatial indexer from mesh
	 * 
	 * @param m
	 */
	public void initialize(Mesh mesh) {

		int dim = mesh.getDimension();

		/*
		 * build the bounding box of the mesh
		 */
		double xmin = Double.POSITIVE_INFINITY, xmax = Double.NEGATIVE_INFINITY;
		double ymin = Double.POSITIVE_INFINITY, ymax = Double.NEGATIVE_INFINITY;
		double zmin = Double.POSITIVE_INFINITY, zmax = Double.NEGATIVE_INFINITY;

		for (int i = 0; i < mesh.countNodes(); i++) {
			double[] p = mesh.getNode(i).getPosition();
			if (p[0] < xmin) {
				xmin = p[0];
			}
			if (p[0] > xmax) {
				xmax = p[0];
			}
			if (p[1] < ymin) {
				ymin = p[1];
			}
			if (p[1] > ymax) {
				ymax = p[1];
			}

			if (dim == 3) {
				if (p[2] < zmin) {
					zmin = p[2];
				}
				if (p[2] > zmax) {
					zmax = p[2];
				}
			}
		}

		x0 = xmin;
		y0 = ymin;

		if (dim == 2) {
			size = Math.max(xmax - xmin, ymax - ymin);
		} else if (dim == 3) {
			z0 = zmin;
			size = Math.max(xmax - xmin, Math.max(ymax - ymin, zmax - zmin));
		}

		if (dim == 2) {
			indexer = new QuadTree(x0, y0, size);
		} else if (dim == 3) {
			indexer = new OctTree(x0, y0, z0, size);
		}

	}

	/**
	 * index the model on the element
	 * 
	 * @param e
	 */
	public void index(Element e) {

		int ng = e.getNumIntegrationPoints();

		for (int j = 0; j < ng; j++) {
			indexer.insert(new IntegrationPointInfo(e, j));
		}

		// indexer.Print(0);
	}

	/**
	 * query a list of integration points lie within a domain
	 * 
	 * @param criteria
	 * @return
	 */
	public Map<Element, ArrayList<Integer>> queryRange(SpatialCriteria criteria) {

		Map<Element, ArrayList<Integer>> map = new TreeMap<Element, ArrayList<Integer>>();

		ArrayList<SpatialInfo> list = indexer.queryRange(criteria);

		for (SpatialInfo i : list) {
			IntegrationPointInfo ip = (IntegrationPointInfo) i;
			Element e = ip.getElement();
			int k = ip.getIpIndex();
			if (map.get(e) == null) {
				map.put(e, new ArrayList<Integer>());
			}
			map.get(e).add(k);
		}

		return map;
	}
}
